package com.atguigu.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;
import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryBrandRelationService;
import com.atguigu.gulimall.product.service.CategoryService;
import com.atguigu.gulimall.product.vo.Catelog2VO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    //    @Autowired
//    CategoryDao categoryDao;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    private String catelogJsonRedisKey = "catelogJson";

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> customListWithTree() {
        List<CategoryEntity> resultList = new ArrayList<>();
        // 查询所有分类
        List<CategoryEntity> allCategoryList = baseMapper.selectList(null);

        if (!CollectionUtils.isEmpty(allCategoryList)) {
            CategoryEntity root = new CategoryEntity();
            root.setCatId(0L);
            resultList = customGetChildren(root, allCategoryList);
        }

        return resultList;
    }

    private List<CategoryEntity> customGetChildren(CategoryEntity root, List<CategoryEntity> allCategoryList) {
        if (ObjectUtils.isEmpty(root) || CollectionUtils.isEmpty(allCategoryList)) {
            return new ArrayList<>();
        }
        return allCategoryList
                .stream()
                // 查询所有以root为parent cid的节点
                .filter(category -> category.getParentCid() == root.getCatId())
                // 查找所有菜单的子菜单
                .peek(category -> category.setChildren(customGetChildren(category, allCategoryList)))
                // 对筛选结果，做排序
                .sorted((c1, c2) -> (c1.getSort() == null ? 0 : c1.getSort()) - (c2.getSort() == null ? 0 : c2.getSort()))
                // 搜集所有菜单
                .collect(Collectors.toList());
    }

    @Override
    public void removeMenuByIds(List<Long> asList) {
        //TODO  1、检查当前删除的菜单，是否被别的地方引用
        if (CollectionUtils.isEmpty(asList)) {
            return;
        }
        //逻辑删除
        baseMapper.deleteBatchIds(asList);
    }

    //[2,25,225]
    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        List<Long> parentPath = findParentPath(catelogId, paths);

//        Collections.reverse(parentPath);


        return parentPath.toArray(new Long[0]);
    }

    /**
     * 级联更新所有关联的数据
     * <p>
     * 使用缓存失效模式
     *
     * @param category
     * @Caching 同时使用多种缓存操作
     */
    @Caching(evict = {
            @CacheEvict(value = {"category"}, key = "'getLevel1Categorys'"),
            @CacheEvict(value = {"category"}, key = "'getCatelogJson'")
    })
    //@CacheEvict(value = {"category"}, allEntries = true)
    @Transactional
    @Override
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);
        categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
    }

    //225,25,2
    private List<Long> findParentPath(Long catelogId, List<Long> paths) {
        CategoryEntity byId = this.getById(catelogId);
        if (byId.getParentCid() != 0) {
            findParentPath(byId.getParentCid(), paths);
        }
        //1、收集当前节点id
        paths.add(catelogId);
        return paths;

    }

    /**
     * 每一个要缓存的数据，都指定名称
     * 推荐按照业务类型划分
     * <p>
     * 默认行为：
     * 1.缓存中有，方法就不调用了
     * 2.缓存的数据，key是默认自动生成的：category::SimpleKey []
     * 3.缓存的value，是jdk序列化后的数据
     * 4.默认ttl时间是：-1
     * <p>
     * 推荐自定义：
     * 1、生成的key
     * 2、缓存的数据ttl时间
     * 3、使用json序列化机制
     *
     * @return
     */
    @Cacheable(value = {"category"}, key = "#root.method.name")
    @Override
    public List<CategoryEntity> getLevel1Categorys() {
        System.out.println("getLevel1Categorys");
        List<CategoryEntity> categoryEntityList = baseMapper.selectList(
                new QueryWrapper<CategoryEntity>().eq("parent_cid", 0)
        );
        return categoryEntityList;
    }

    /**
     * 自定义方式
     * @return
     */
    @Override
    public Map<String, List<Catelog2VO>> getCatelogJson() {
        /**
         * 1、空值缓存：解决缓存穿透
         * 2、设置随机过期时间：解决缓存雪崩
         * 3、加锁：解决缓存击穿
         */
        Map<String, List<Catelog2VO>> catelogJsonMap = getCache();
        if (catelogJsonMap == null) {
            // 缓存中没有数据,查询数据库
            catelogJsonMap = getCatelogJsonFromDBWithRedisson();
        }
        return catelogJsonMap;
    }


    /**
     * spring cache方式
     * @return
     */
//    @Cacheable(value = {"category"}, key = "#root.method.name",sync = true)
//    @Override
//    public Map<String, List<Catelog2VO>> getCatelogJson() {
//        return getCatelogJsonFromDB();
//    }

    /**
     * 从数据库查询，并封装数据
     * <p>
     * 本地synchronized锁，只能锁住当前进程的资源，分布式情况下，只能使用分布式锁
     *
     * @return
     */
    private Map<String, List<Catelog2VO>> getCatelogJsonFromDB() {
        System.out.println("查询了数据库......");
        // 优化1 将循环查询数据库，改为只查询1次数据库，后续都从总结果里抽取
        List<CategoryEntity> categoryList = baseMapper.selectList(null);
        // 查询出所有1级分类
        List<CategoryEntity> level1Categorys = getLevel1Categorys();
        // 封装数据
        Map<String, List<Catelog2VO>> map = level1Categorys.stream()
                .collect(Collectors.toMap(
                        key -> key.getCatId().toString(),
                        v -> {
                            // 查询2级分类
                            List<CategoryEntity> category2List = getParentCid(categoryList, v.getCatId());
                            List<Catelog2VO> catelog2VOList = null;
                            if (!CollectionUtils.isEmpty(category2List)) {
                                catelog2VOList = category2List.stream()
                                        .map(level2 -> {
                                            String catelog2Id = v.getCatId().toString();
                                            // 查询3级分类
                                            List<CategoryEntity> category3List = getParentCid(categoryList, level2.getCatId());
                                            List<Catelog2VO.Catelog3VO> category3VOList = null;
                                            if (!CollectionUtils.isEmpty(category3List)) {
                                                category3VOList = category3List.stream()
                                                        .map(catelog3 -> {
                                                            return new Catelog2VO.Catelog3VO(
                                                                    catelog2Id,
                                                                    catelog3.getCatId().toString(),
                                                                    catelog3.getName()
                                                            );
                                                        })
                                                        .collect(Collectors.toList());
                                            }
                                            Catelog2VO catelog2VO = new Catelog2VO(
                                                    catelog2Id,
                                                    category3VOList,
                                                    level2.getCatId().toString(),
                                                    level2.getName()
                                            );
                                            return catelog2VO;
                                        })
                                        .collect(Collectors.toList());
                            }
                            return catelog2VOList;
                        }
                ));
        return map;
    }

    private Map<String, List<Catelog2VO>> getCatelogJsonFromDBWithRedisLock() {
        String lockKey = "lock";
        String lockValue = Thread.currentThread().getName() + "|" + UUID.randomUUID().toString();
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        while (true) {
            // 加锁，设置过期时间(原子加锁)，避免程序崩溃 死锁问题
            Boolean locked = ops.setIfAbsent(
                    lockKey,
                    lockValue,
                    300,
                    TimeUnit.SECONDS
            );
            // 每次尝试加锁后，都查一下缓存是否有数据，有就直接返回
            Map<String, List<Catelog2VO>> catelogJsonMap = getCache();
            if (catelogJsonMap != null) {
                return catelogJsonMap;
            }
            if (locked) {
                // 加锁成功
                System.out.println("获取分布式锁成功......");
                try {
                    // 执行业务逻辑
                    catelogJsonMap = getCatelogJsonFromDB();
                    // 将数据序列化为json 存入redis  (json的好处是跨平台)
                    ops.set(catelogJsonRedisKey, JSON.toJSONString(catelogJsonMap), 1, TimeUnit.DAYS);
                    return catelogJsonMap;
                } finally {
                    // todo 删除锁 版本1 （删除的可能不是自己加的锁）非原子
//                redisTemplate.delete(lockKey);
                    // todo 只能删自己加的锁 版本2  （删除的也可能不是自己加的锁）非原子
//                String value = redisTemplate.opsForValue().get(lockKey);
//                if (lockValue.equalsIgnoreCase(value)){
//                    redisTemplate.delete(lockKey);
//                }
                    // todo lua脚本 原子解锁 版本3(终版)
                    String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                    Boolean execute = redisTemplate.execute(
                            new DefaultRedisScript<>(script, Boolean.class),
                            Collections.singletonList(lockKey),
                            lockValue
                    );
                    if (execute) {
                        // 删除锁成功
                        System.out.println("删除锁成功");
                    }
                }
            } else {
                // 加锁失败 每100毫秒 自旋
                System.out.println("加锁失败 每100毫秒 自旋......");
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public Map<String, List<Catelog2VO>> getCatelogJsonFromDBWithRedisson() {
        /**
         * 锁的粒度
         * 具体锁某个数据，例如 11号-商品：product-11-lock
         */
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        String lockKey = "catelogJson-lock";
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
        try {
            // 双重检查，加锁后，再次查询缓存
            String catelogJson = ops.get(catelogJsonRedisKey);
            if (!StringUtils.isEmpty(catelogJson)) {
                return JSON.parseObject(
                        catelogJson,
                        new TypeReference<Map<String, List<Catelog2VO>>>() {
                        }
                );
            }
            // 执行业务逻辑
            Map<String, List<Catelog2VO>> catelogJsonFromDB = getCatelogJsonFromDB();
            // 将数据序列化为json 存入redis  (json的好处是跨平台)
            ops.set(catelogJsonRedisKey, JSON.toJSONString(catelogJsonFromDB), 1, TimeUnit.DAYS);
            return catelogJsonFromDB;
        } finally {
            lock.unlock();
        }
    }

    private Map<String, List<Catelog2VO>> getCatelogJsonFromDBWithRedisson2() {
        /**
         * 1、空值缓存：解决缓存穿透
         * 2、设置随机过期时间：解决缓存雪崩
         * 3、加锁：解决缓存击穿
         */
        /**
         * 锁的粒度
         * 具体锁某个数据，例如 11号-商品：product-11-lock
         */
        String lockKey = "catelogJson-lock";
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
        try {
            // 双重检查，加锁后，再次查询缓存
            return getCatelogJsonFromDB();
        } finally {
            lock.unlock();
        }
    }

    private List<CategoryEntity> getParentCid(List<CategoryEntity> categoryList, Long parentCid) {
//        return baseMapper.selectList(
//                new QueryWrapper<CategoryEntity>()
//                        .eq("parent_cid", v.getCatId())
//        );
        return categoryList.stream()
                .filter(item -> {
                    return item.getParentCid() == parentCid;
                })
                .collect(Collectors.toList());
    }

    private Map<String, List<Catelog2VO>> getCache() {
        String catelogJson = redisTemplate.opsForValue().get(catelogJsonRedisKey);
        if (!StringUtils.isEmpty(catelogJson)) {
            System.out.println("缓存命中！");
            return JSON.parseObject(
                    catelogJson,
                    new TypeReference<Map<String, List<Catelog2VO>>>() {
                    }
            );
        }
        System.out.println("缓存未命中......");
        return null;
    }
}